﻿using System;
using System.Buffers;
using System.IO;
using System.Net.Security;
using System.Threading.Tasks;
using ePipe;
using eTask.Extensions;
using Letter.IO.Memorizer;
using Letter.Net.SSL;

namespace Letter.Net.Tcp;


public abstract class ATcpConnection : AConnection
{
    public ATcpConnection() : this(null, null)
    {
    }

    public ATcpConnection(SSLOptions sslOptions, Func<Stream, SslStream> sslStreamFactory)
    {
        if (sslOptions != null && sslStreamFactory != null)
        {
            this.sslConnection = true;
            this.sslOptions = sslOptions;
            this.sslStreamFactory = sslStreamFactory;
        }
        else if (sslOptions == null && sslStreamFactory == null)
        {
            this.sslConnection = false;
        }
        else
        {
            if (sslOptions == null)
            {
                throw new ArgumentNullException(nameof(sslOptions));
            }

            if (sslStreamFactory == null)
            {
                throw new ArgumentNullException(nameof(sslStreamFactory));
            }
        }
    }

    private bool sslConnection;
    private SSLOptions sslOptions;
    private Func<Stream, SslStream> sslStreamFactory;
    
    private IDuplexPipe<byte> transport;
    private Task transportTask;

    protected override IDuplexPipe<byte> Transport
    {
        get { return this.transport; }
    }
    
    public override void OnActive()
    {
        if (this.sslConnection)
        {
            var inputPipeOptions = StreamPipeOptionsHelper.ReaderOptionsCreator(MemoryPoolProvider.ShareMemoryPool<byte>());
            var outputPipeOptions = StreamPipeOptionsHelper.WriterOptionsCreator(MemoryPoolProvider.ShareMemoryPool<byte>());
            this.transport = new SslStreamDuplexPipe(base.Transport, inputPipeOptions, outputPipeOptions, sslStreamFactory);
        }
        else
        {
            this.transport = base.Transport;
        }

        this.ActiveAsync().Ignore();
    }

    private async Task ActiveAsync()
    {
        if (this.sslConnection)
        {
            var tlsOptions = this.sslOptions;
            var sslDuplexPipe = this.transport as SslStreamDuplexPipe;
            switch (tlsOptions)
            {
                case SSLServerOptions serverTlsOptions:
                    await sslDuplexPipe.Stream.AuthenticateAsServerAsync(
                        serverTlsOptions.Certificate,
                        serverTlsOptions.NegotiateClientCertificate,
                        serverTlsOptions.EnabledProtocols,
                        serverTlsOptions.CheckCertificateRevocation);
                    break;
                case SSLClientOptions clientTlsOptions:
                    await sslDuplexPipe.Stream.AuthenticateAsClientAsync(
                        clientTlsOptions.TargetHost,
                        clientTlsOptions.X509CertificateCollection,
                        clientTlsOptions.EnabledProtocols,
                        clientTlsOptions.CheckCertificateRevocation);
                    break;
            }
        }

        this.transportTask = this.StartReadAsync();
    }

    private async Task StartReadAsync()
    {
        var input = this.Transport.Reader;
        while (true)
        {
            var result = await input.ReadAsync();
            if (result.IsCanceled || result.IsCompleted)
            {
                break;
            }

            var buf = result.Buffer;
            this.OnRcvBuffer(input, ref buf);
        }
        
        await input.CompleteAsync();
    }

    protected abstract void OnRcvBuffer(IPipeReader<byte> input, ref ReadOnlySequence<byte> buf);

    public override void OnException(Exception ex)
    {
    }

    public override void OnInactive()
    {
        if (this.transport is SslStreamDuplexPipe sslDuplexPipe)
        {
            sslDuplexPipe.DisposeAsync().GetAwaiter().GetResult();
        }

        if (this.transportTask != null)
        {
            this.transportTask.GetAwaiter().GetResult();
        }
    }
}
